General Set UP

Library

library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels) 
library(probably)
library(vip)
library(plotly)
library(ClusterR)
library(cluster)
library(vip)
library(gt)
tidymodels_prefer()
theme_set(theme_bw())       
Sys.setlocale("LC_TIME", "English")
## [1] "English_United States.1252"
set.seed(74)

Read in data

breastCa<-read_csv(file = "breast-cancer.csv")

Regression (Least Square & LASSO & GAM)

Data cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(radius_mean:fractal_dimension_mean) 

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-8)

Creation of cv folds

breastCa_Re_CV<-vfold_cv(breastCa_Re, v = 10)

Model spec (Least Square & LASSO)

#least square
lm_spec <-
    linear_reg() %>% 
    set_engine(engine = 'lm') %>% 
    set_mode('regression')

#LASSO
lm_lasso_spec <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>% ## mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>% #note we are using a different engine
  set_mode('regression')

Recipes & workflows (Least Square & LASSO)

#least square
least_rec <- recipe(area_mean ~ ., data = breastCa_Re) %>%
    step_corr(all_predictors()) %>% 
    step_nzv(all_predictors()) %>% # removes variables with the same value
    step_normalize(all_numeric_predictors()) %>% # important standardization step for LASSO
    step_dummy(all_nominal_predictors())

least_lm_wf <- workflow() %>%
    add_recipe(least_rec) %>%
    add_model(lm_spec)
    
#LASSO
lasso_wf<- workflow() %>% 
  add_recipe(least_rec) %>%
  add_model(lm_lasso_spec) 

Fit & tune models (Least Square & LASSO)

#least square
least_fit <- fit(least_lm_wf, data = breastCa_Re) 

least_fit %>% tidy()
#LASSO
#tune
penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)), #log10 transformed 
  levels = 30)

tune_output <- tune_grid( # new function for tuning hyperparameters
  lasso_wf, # workflow
  resamples = breastCa_Re_CV, # cv folds
  metrics = metric_set(rmse, mae),
  grid = penalty_grid # penalty grid defined above
)

#fit
best_se_penalty <- select_by_one_std_err(tune_output, metric = 'mae', desc(penalty))
final_wf_se <- finalize_workflow(lasso_wf, best_se_penalty)
lasso_fit <- fit(final_wf_se , data = breastCa_Re)
lasso_fit %>% tidy()

Calculate and collect CV metrics (Least Square & LASSO)

# Least Square model
least_fit_cv <- fit_resamples(least_lm_wf,
  resamples = breastCa_Re_CV, 
  metrics = metric_set(rmse, mae)
)

least_fit_cv %>% collect_metrics(summarize = TRUE)
# LASSO model
tune_output %>% 
  collect_metrics() %>% 
  filter(penalty == (best_se_penalty 
                     %>% pull(penalty)))

Residual Plots (Least Square & LASSO)

#least square
least_fit_output <- least_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(least_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

#LASSO
lasso_fit_output <- lasso_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(lasso_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

GAM

Accounting for nonlinearity

set.seed(123)

gam_spec <- 
  gen_additive_mod() %>%
  set_engine(engine = 'mgcv') %>%
  set_mode('regression') 

gam_mod <- fit(gam_spec,
    area_mean ~ s(radius_mean)+texture_mean+s(perimeter_mean)+smoothness_mean+compactness_mean+concave_points_mean+concavity_mean+symmetry_mean+fractal_dimension_mean,
    data = breastCa_Re_new
)

par(mfrow=c(2,2))
gam_mod %>% pluck('fit') %>% mgcv::gam.check() 

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 19 iterations.
## The RMS GCV score gradient at convergence was 3.693889e-05 .
## The Hessian was positive definite.
## Model rank =  26 / 26 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                     k'  edf k-index p-value  
## s(radius_mean)    9.00 8.73    0.91   0.015 *
## s(perimeter_mean) 9.00 9.00    0.96   0.170  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
gam_mod %>% pluck('fit') %>% summary()
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## area_mean ~ s(radius_mean) + texture_mean + s(perimeter_mean) + 
##     smoothness_mean + compactness_mean + concave_points_mean + 
##     concavity_mean + symmetry_mean + fractal_dimension_mean
## 
## Parametric coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             676.8849     9.4284  71.792  < 2e-16 ***
## texture_mean              0.2863     0.1136   2.521 0.012000 *  
## smoothness_mean         176.4116    55.3592   3.187 0.001522 ** 
## compactness_mean       -559.1059    41.8880 -13.348  < 2e-16 ***
## concave_points_mean    -193.1237    55.0443  -3.509 0.000488 ***
## concavity_mean           75.1818    19.7185   3.813 0.000153 ***
## symmetry_mean            24.5876    21.8517   1.125 0.261001    
## fractal_dimension_mean  193.2815   164.7137   1.173 0.241134    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                     edf Ref.df     F p-value    
## s(radius_mean)    8.726  8.982 79.72  <2e-16 ***
## s(perimeter_mean) 9.000  9.000 36.36  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.999   Deviance explained = 99.9%
## GCV = 115.47  Scale est. = 110.25    n = 569
ns_rec <- least_rec %>%
  step_ns(x, deg_free = 9)
ns9_wf <- workflow()  %>%
  add_recipe(ns_rec) %>%
  add_model(lm_spec)

hist(breastCa_Re_new$area_mean)

gam_mod %>% pluck('fit') %>% plot()

Evaluation of the GAM model on Test Data

gam_test_output <- gam_mod %>%
  predict(new_data=breastCa_Re_new) %>%
  bind_cols(breastCa_Re_new %>% select(area_mean))

gam_test_output %>%
  rmse(truth=area_mean, estimate=.pred) 
gam_test_output %>%
  mae(truth=area_mean, estimate=.pred) 
gam_test_output %>%
  tidy() %>% 
  gt()
column n mean sd median trimmed mad min max range skew kurtosis se
.pred 569 654.8891 351.7617 548.9523 606.1209 151.2236 142.4962 2498.764 2356.268 1.634433 6.548502 14.74662
area_mean 569 654.8891 351.9141 551.1000 606.1278 153.3000 143.5000 2501.000 2357.500 1.641391 6.609761 14.75301

Classification

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10)

LASSO and Logistic Regression

Implete Lasso Logistic Regression in tidymodels

# Make sure you set reference level (to the outcome you are NOT interested in)
breastCa_Re_new2 <- breastCa_Re_new%>%
  mutate(diagnosis = relevel(factor(diagnosis ), ref='B')) #set reference level

data_cv10 <- vfold_cv(breastCa_Re_new2, v = 10)


# Logistic LASSO Regression Model Spec
logistic_lasso_spec_tune <- logistic_reg() %>%
    set_engine('glmnet') %>%
    set_args(mixture = 1, penalty = tune()) %>%
    set_mode('classification')

# Recipe
logistic_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2) %>%
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors())

# Workflow (Recipe + Model)
log_lasso_wf <- workflow() %>% 
    add_recipe(logistic_rec) %>%
    add_model(logistic_lasso_spec_tune) 

# Tune Model (trying a variety of values of Lambda penalty)
penalty_grid <- grid_regular(
  penalty(range = c(-5, 1)), #log10 transformed  (kept moving min down from 0)
  levels = 100)

tune_output <- tune_grid( 
  log_lasso_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(roc_auc,accuracy),
  control = control_resamples(save_pred = TRUE, event_level = 'second'),
  grid = penalty_grid # penalty grid defined above
)

# Visualize Model Evaluation Metrics from Tuning
autoplot(tune_output) + theme_classic()

Inspecting the Model

best_se_penalty <- select_by_one_std_err(tune_output, metric = 'roc_auc', desc(penalty)) # choose penalty value based on the largest penalty within 1 se of the highest CV roc_auc
final_fit_se <- finalize_workflow(log_lasso_wf, best_se_penalty) %>% # incorporates penalty value to workflow 
    fit(data = breastCa_Re_new2)

final_fit_se %>% tidy()
final_fit_se %>% tidy() %>%
  filter(estimate == 0)
#variable importance
glmnet_output <- final_fit_se %>% extract_fit_engine()
    
# Create a boolean matrix (predictors x lambdas) of variable exclusion
bool_predictor_exclude <- glmnet_output$beta==0

# Loop over each variable
var_imp <- sapply(seq_len(nrow(bool_predictor_exclude)), function(row) {
    # Extract coefficient path (sorted from highest to lowest lambda)
    this_coeff_path <- bool_predictor_exclude[row,]
    # Compute and return the # of lambdas until this variable is out forever
    ncol(bool_predictor_exclude) - which.min(this_coeff_path) + 1
})

# Create a dataset of this information and sort
var_imp_data <- tibble(
    var_name = rownames(bool_predictor_exclude),
    var_imp = var_imp
)
var_imp_data %>% arrange(desc(var_imp))

Evaluation Metrics

# CV results for "best lambda"
tune_output %>%
    collect_metrics() %>%
    filter(penalty == best_se_penalty %>% pull(penalty))
# Count up number of B and M in the training data
breastCa_Re_new2 %>%
    count(diagnosis) # Name of the outcome variable goes inside count()
#Compute the NIR
NIR<- 357/(357+212)
NIR
## [1] 0.6274165

Threshold

# Soft Predictions on Training Data
final_output <-
  final_fit_se %>% predict(new_data = breastCa_Re_new2, type = 'prob') %>%     bind_cols(breastCa_Re_new2)



final_output %>%
  ggplot(aes(x = diagnosis, y = .pred_M)) +
  geom_boxplot()

# Use soft predictions
final_output %>%
    roc_curve(diagnosis,.pred_M,event_level = 'second') %>%
    autoplot()

# thresholds in terms of reference level
threshold_output <- final_output %>%
    threshold_perf(truth = diagnosis, estimate = .pred_B, thresholds = seq(0,1,by=.01)) 

# J-index v. threshold for not M
threshold_output %>%
    filter(.metric == 'j_index') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'J-index', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'j_index') %>%
    arrange(desc(.estimate))
# Distance v. threshold for not M

threshold_output %>%
    filter(.metric == 'distance') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'Distance', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'distance') %>%
    arrange(.estimate)
log_metrics <- metric_set(accuracy,sens,yardstick::spec)

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_B, levels(diagnosis), threshold = .64)) %>%
    log_metrics(truth = diagnosis, estimate = .pred_class, event_level = 'second')

Random Forest

Building Random Forest

# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(mtry = NULL, # size of random subset of variables; default is floor(sqrt(ncol(x)))
           trees = 1000, # Number of trees
           min_n = 2,
           probability = FALSE, # FALSE: hard predictions
           importance = 'impurity') %>% 
  set_mode('classification') # change this for regression tree

# Recipe
data_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2)

# Workflows
data_wf_mtry2 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 2)) %>%
  add_recipe(data_rec)

# Create workflows for mtry = 4 , 10, and 20
data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec)

data_wf_mtry10 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 10)) %>%
  add_recipe(data_rec)

data_wf_mtry20 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 20)) %>%
  add_recipe(data_rec)
# Fit Models

set.seed(123) # make sure to run this before each fit so that you have the same 1000 trees
data_fit_mtry2 <- fit(data_wf_mtry2, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry4 <- fit(data_wf_mtry4, data = breastCa_Re_new2)

set.seed(123) 
data_fit_mtry10 <- fit(data_wf_mtry10, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry20 <- fit(data_wf_mtry20, data = breastCa_Re_new2)
# Custom Function to get OOB predictions, true observed outcomes and add a model label
rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_diagnosis = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          diagnosis = truth,
          model = model_label
      )
}

#check out the function output
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis))
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(
    rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry4,'mtry4', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry10,'mtry10', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry20,'mtry20', breastCa_Re_new2 %>% pull(diagnosis))
)


data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate = .pred_diagnosis)

Preliminary interpretation

data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate =.pred_diagnosis) %>%
  mutate(mtry = as.numeric(stringr::str_replace(model,'mtry',''))) %>%
  ggplot(aes(x = mtry, y = .estimate )) + 
  geom_point() +
  geom_line() +
  theme_classic()

Evaluating the forest

data_fit_mtry2
## == Workflow [trained] ==========================================================
## Preprocessor: Recipe
## Model: rand_forest()
## 
## -- Preprocessor ----------------------------------------------------------------
## 0 Recipe Steps
## 
## -- Model -----------------------------------------------------------------------
## Ranger result
## 
## Call:
##  ranger::ranger(x = maybe_data_frame(x), y = y, mtry = min_cols(~2,      x), num.trees = ~1000, min.node.size = min_rows(~2, x), probability = ~FALSE,      importance = ~"impurity", num.threads = 1, verbose = FALSE,      seed = sample.int(10^5, 1)) 
## 
## Type:                             Classification 
## Number of trees:                  1000 
## Sample size:                      569 
## Number of independent variables:  20 
## Mtry:                             2 
## Target node size:                 2 
## Variable importance mode:         impurity 
## Splitrule:                        gini 
## OOB prediction error:             3.51 %
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)) %>%
    conf_mat(truth = diagnosis, estimate= .pred_diagnosis)
##           Truth
## Prediction   B   M
##          B 351  14
##          M   6 198

Variable importance measures

data_fit_mtry2 %>% 
    extract_fit_engine() %>% 
    vip(num_features = 30) + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = area_worst)) +
    geom_violin() + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = fractal_dimension_mean)) +
    geom_violin() + theme_classic()

#intermediate important
ggplot(breastCa_Re_new2, aes(x = diagnosis, y = perimeter_mean)) +
    geom_violin() + theme_classic()

Clustering

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10) 
ggplot(breastCa_Re_new, aes(x = perimeter_mean, y = `concave points_worst`)) +
    geom_point() +
    theme_classic()

K-means clustering on perimeter_mean and concave points_worst

# Select just the perimeter_mean and concave points_worst variables
breastCa_Re_new_sub <- breastCa_Re_new %>%
    select(perimeter_mean, `concave points_worst`)

# Run k-means for k = centers = 2
set.seed(253)
kclust_k2 <- kmeans(breastCa_Re_new_sub, centers = 2)

# Display the cluster assignments
kclust_k2$cluster
##   [1] 2 2 2 1 2 1 2 1 1 1 2 2 2 2 1 1 1 2 2 1 1 1 2 2 2 2 1 2 2 2 2 1 2 2 2 2 1
##  [38] 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1
##  [75] 1 2 1 2 2 1 1 1 2 2 1 2 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1
## [112] 1 1 1 1 1 1 1 2 2 1 2 2 1 1 1 1 2 1 2 1 1 2 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1
## [149] 1 1 1 1 1 1 1 1 2 2 1 1 1 2 2 1 2 1 1 2 2 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 1
## [186] 1 2 1 1 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 1 1 2 1 1 2 1 2 2 1 1 1 1 2 2 1 1
## [223] 1 2 1 1 1 1 1 1 2 1 1 2 1 1 2 2 1 2 1 1 1 1 2 1 1 1 1 1 2 1 2 2 2 1 2 2 2
## [260] 2 2 2 2 1 2 2 1 1 1 1 1 1 2 1 2 1 1 2 1 1 2 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 2 1 1 1 1 2 2 2 1 1
## [334] 1 1 2 1 2 1 2 1 1 1 2 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 2 2
## [371] 2 1 2 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 2
## [408] 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 1 1 1 1 1 1 2 1 1
## [445] 2 1 2 1 1 2 1 2 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 2 1 1 2 1 2 1 2 2 1 1 1 1 1 2 2 1 1 1 2 1 1 1 1 2 2 1 1 1 1 1 1 2 2
## [519] 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 2 2 2 2 2 1
# Add a variable (kclust_k2) to the original dataset 
# containing the cluster assignments
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2 = factor(kclust_k2$cluster))
# Visualize the cluster assignments on the original scatterplot
originalClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(originalClusterPlot  , tooltip = c( "text"))

Addressing variable scale

# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k2_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 2)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2_scale = factor(kclust_k2_scale$cluster))

# Visualize the new cluster assignments
scaledClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(scaledClusterPlot  , tooltip = c( "text"))

Clustering on more variables

# Select the variables to be used in clustering
breastCa_Re_new_sub2 <- breastCa_Re_new %>%
    select(c(2:21))

# Look at summary statistics of the 3 variables
summary(breastCa_Re_new_sub2)
##   radius_mean      texture_mean   perimeter_mean     area_mean     
##  Min.   : 6.981   Min.   : 9.71   Min.   : 43.79   Min.   : 143.5  
##  1st Qu.:11.700   1st Qu.:16.17   1st Qu.: 75.17   1st Qu.: 420.3  
##  Median :13.370   Median :18.84   Median : 86.24   Median : 551.1  
##  Mean   :14.127   Mean   :19.29   Mean   : 91.97   Mean   : 654.9  
##  3rd Qu.:15.780   3rd Qu.:21.80   3rd Qu.:104.10   3rd Qu.: 782.7  
##  Max.   :28.110   Max.   :39.28   Max.   :188.50   Max.   :2501.0  
##  smoothness_mean   compactness_mean  concavity_mean    concave points_mean
##  Min.   :0.05263   Min.   :0.01938   Min.   :0.00000   Min.   :0.00000    
##  1st Qu.:0.08637   1st Qu.:0.06492   1st Qu.:0.02956   1st Qu.:0.02031    
##  Median :0.09587   Median :0.09263   Median :0.06154   Median :0.03350    
##  Mean   :0.09636   Mean   :0.10434   Mean   :0.08880   Mean   :0.04892    
##  3rd Qu.:0.10530   3rd Qu.:0.13040   3rd Qu.:0.13070   3rd Qu.:0.07400    
##  Max.   :0.16340   Max.   :0.34540   Max.   :0.42680   Max.   :0.20120    
##  fractal_dimension_mean  radius_worst   texture_worst   perimeter_worst 
##  Min.   :0.04996        Min.   : 7.93   Min.   :12.02   Min.   : 50.41  
##  1st Qu.:0.05770        1st Qu.:13.01   1st Qu.:21.08   1st Qu.: 84.11  
##  Median :0.06154        Median :14.97   Median :25.41   Median : 97.66  
##  Mean   :0.06280        Mean   :16.27   Mean   :25.68   Mean   :107.26  
##  3rd Qu.:0.06612        3rd Qu.:18.79   3rd Qu.:29.72   3rd Qu.:125.40  
##  Max.   :0.09744        Max.   :36.04   Max.   :49.54   Max.   :251.20  
##    area_worst     smoothness_worst  compactness_worst concavity_worst 
##  Min.   : 185.2   Min.   :0.07117   Min.   :0.02729   Min.   :0.0000  
##  1st Qu.: 515.3   1st Qu.:0.11660   1st Qu.:0.14720   1st Qu.:0.1145  
##  Median : 686.5   Median :0.13130   Median :0.21190   Median :0.2267  
##  Mean   : 880.6   Mean   :0.13237   Mean   :0.25427   Mean   :0.2722  
##  3rd Qu.:1084.0   3rd Qu.:0.14600   3rd Qu.:0.33910   3rd Qu.:0.3829  
##  Max.   :4254.0   Max.   :0.22260   Max.   :1.05800   Max.   :1.2520  
##  concave points_worst symmetry_worst   fractal_dimension_worst
##  Min.   :0.00000      Min.   :0.1565   Min.   :0.05504        
##  1st Qu.:0.06493      1st Qu.:0.2504   1st Qu.:0.07146        
##  Median :0.09993      Median :0.2822   Median :0.08004        
##  Mean   :0.11461      Mean   :0.2901   Mean   :0.08395        
##  3rd Qu.:0.16140      3rd Qu.:0.3179   3rd Qu.:0.09208        
##  Max.   :0.29100      Max.   :0.6638   Max.   :0.20750        
##  concave_points_mean
##  Min.   :0.00000    
##  1st Qu.:0.02031    
##  Median :0.03350    
##  Mean   :0.04892    
##  3rd Qu.:0.07400    
##  Max.   :0.20120
set.seed(253)
kclust_k2_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 2)

breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k2_allvars = factor(kclust_k2_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k2_allvars)

Interpreting the clusters

breastCa_Re_new %>%
    group_by(kclust_k2_allvars) %>%
    summarize(across(c(2:21), mean))

Picking k

# Data-specific function to cluster and calculate total within-cluster SS
breastCa_Re_new_cluster_ss <- function(k){
    # Perform clustering
    kclust <- kmeans(scale(breastCa_Re_new_sub2), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(
    k = 1:20,
    tot_wc_ss = purrr::map_dbl(1:20, breastCa_Re_new_cluster_ss)
) %>% 
    ggplot(aes(x = k, y = tot_wc_ss)) +
    geom_point() + 
    geom_line()+
    labs(x = "Number of clusters",y = 'Total within-cluster sum of squares') + 
    theme_classic()

Normalized variables clustering visualizaiton of optimal K

# Run k-means for k = centers = 3
set.seed(253)
kclust_k3 <- kmeans(breastCa_Re_new_sub, centers = 3)

# Display the cluster assignments
kclust_k3$cluster
##   [1] 3 3 3 1 3 2 3 2 2 2 2 2 3 2 2 2 2 2 3 2 2 1 2 3 2 3 2 3 2 3 3 1 3 3 2 2 2
##  [38] 2 2 2 2 1 3 2 2 3 1 2 1 2 1 2 1 3 2 1 3 2 2 1 1 1 2 1 2 2 1 1 1 1 3 1 3 2
##  [75] 1 2 2 3 3 2 1 2 3 3 1 3 2 3 1 2 2 2 2 2 2 3 1 1 1 2 2 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 3 1 3 3 2 2 2 2 3 2 3 1 2 2 2 3 1 1 1 2 1 1 2 1 2 1 1 1 2
## [149] 2 2 2 1 1 1 2 1 3 2 1 1 1 3 3 1 3 2 1 2 3 2 1 2 2 1 1 1 1 2 1 1 3 3 2 1 2
## [186] 1 3 1 1 1 2 1 1 1 2 2 2 3 3 2 1 3 3 2 1 2 1 2 2 2 3 1 3 3 2 2 1 1 3 3 2 2
## [223] 1 2 2 2 1 2 1 2 3 1 1 3 1 2 3 3 2 3 2 1 1 2 3 1 2 2 1 1 3 1 3 3 3 2 3 2 2
## [260] 2 3 2 3 2 2 3 1 2 1 1 2 1 3 1 3 1 1 3 2 2 3 1 3 2 2 1 1 1 1 1 2 2 2 1 1 2
## [297] 1 1 2 1 3 1 3 1 1 1 2 1 2 2 1 2 1 1 1 1 1 3 1 1 1 3 2 3 1 1 2 1 2 2 2 2 1
## [334] 1 1 2 2 3 1 3 2 1 1 3 1 1 1 2 1 1 1 2 3 2 1 1 2 2 1 1 1 2 1 2 2 3 3 1 3 3
## [371] 2 2 3 3 2 2 1 2 2 1 1 1 1 1 2 2 1 2 1 3 1 1 2 3 1 2 2 2 1 1 3 1 2 2 1 1 2
## [408] 2 3 1 1 1 1 2 2 1 1 2 1 1 1 2 1 2 1 1 1 1 1 1 2 1 3 3 2 2 2 2 2 2 1 2 2 1
## [445] 3 1 3 2 2 3 1 3 1 2 1 2 1 2 2 1 2 3 2 1 2 2 2 1 3 1 1 1 2 1 1 2 2 2 1 2 1
## [482] 2 2 2 2 2 2 3 1 2 1 3 3 1 2 2 2 1 3 3 2 2 1 3 1 1 1 1 2 2 1 2 2 2 2 1 3 3
## [519] 2 2 1 3 1 2 1 1 2 1 2 1 1 1 2 3 1 3 2 1 1 1 1 2 2 2 2 2 1 1 1 1 1 1 1 1 2
## [556] 1 1 1 2 1 2 1 2 3 3 3 2 3 1
# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k3_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 3)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_3_scale = factor(kclust_k3_scale$cluster))

# Visualize the new cluster assignments
newClusterKPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_3_scale,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(newClusterKPlot  , tooltip = c( "text"))

Perform clustering on more variabels with K=3

set.seed(253)
kclust_k3_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 3)
#within clusters su of squares
kclust_k3_allvars
## K-means clustering with 3 clusters of sizes 370, 93, 106
## 
## Cluster means:
##   radius_mean texture_mean perimeter_mean   area_mean smoothness_mean
## 1 -0.47294644   -0.2447179    -0.49151681 -0.46834420      -0.3393479
## 2 -0.01575391    0.2342339     0.05989892 -0.08895702       0.9405564
## 3  1.66467260    0.6486969     1.66311907  1.71283356       0.3593111
##   compactness_mean concavity_mean concave points_mean fractal_dimension_mean
## 1       -0.5408815     -0.5826564          -0.5916723             -0.1707039
## 2        1.1632023      0.9455177           0.6854667              1.0983509
## 3        0.8674372      1.2042428           1.4638711             -0.3677942
##   radius_worst texture_worst perimeter_worst  area_worst smoothness_worst
## 1   -0.5121756    -0.2770056      -0.5286914 -0.49367809       -0.3671331
## 2    0.1171932     0.4474237       0.2045023  0.01879255        1.0989419
## 3    1.6849621     0.5743552       1.6660104  1.70672818        0.3173362
##   compactness_worst concavity_worst concave points_worst symmetry_worst
## 1        -0.5208214      -0.5605965           -0.6036665     -0.3364717
## 2         1.3364447       1.2237501            0.9683800      1.0287464
## 3         0.6454203       0.8831314            1.2575212      0.2718973
##   fractal_dimension_worst concave_points_mean
## 1             -0.37590256          -0.5916723
## 2              1.43420972           0.6854667
## 3              0.05379665           1.4638711
## 
## Clustering vector:
##   [1] 2 3 3 2 3 2 3 2 2 2 1 2 3 1 2 2 1 2 3 1 1 1 2 3 3 2 2 3 2 3 3 2 3 3 2 2 2
##  [38] 1 1 2 1 2 3 2 2 3 1 2 1 1 1 1 1 3 1 1 3 2 1 1 1 1 2 1 2 2 1 1 2 1 3 1 2 1
##  [75] 1 1 1 3 3 1 1 2 3 3 1 3 1 3 1 1 1 1 1 1 2 3 1 1 1 2 1 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 1 1 3 3 1 1 1 1 3 2 3 1 2 2 1 3 1 1 1 2 1 1 1 1 1 1 1 2 1
## [149] 1 1 1 2 2 1 1 1 3 1 1 1 1 3 3 1 3 1 1 1 3 1 1 1 2 1 1 1 1 2 1 1 3 3 2 1 1
## [186] 1 3 1 1 1 2 1 1 2 2 1 2 1 3 2 1 3 3 2 1 1 1 1 2 1 3 1 3 1 2 2 1 1 3 3 1 1
## [223] 1 2 1 1 1 1 1 2 2 1 1 3 1 1 3 3 1 3 1 1 2 1 3 1 1 2 1 1 3 1 3 3 3 1 3 2 2
## [260] 2 3 1 3 1 3 3 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 3 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 2 1 1 3 1 3 1 1 1 1 2 2 2 1 1
## [334] 1 1 3 1 3 1 3 1 1 1 3 1 1 1 1 1 1 1 2 3 2 1 1 1 1 1 1 1 1 1 1 1 3 3 1 3 3
## [371] 2 1 3 3 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 3 1 1 2 3 1 1 1 1 1 1 2 1 1 1 1 1 1
## [408] 1 3 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 1 1 1 1 2 1 3 3 1 2 1 1 1 1 1 3 1 1
## [445] 3 1 3 1 1 3 1 3 1 1 1 1 1 1 1 1 3 3 1 1 1 2 1 1 3 2 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 1 2 1 3 1 1 1 1 3 1 1 1 2 1 3 3 1 2 1 3 2 2 1 1 1 2 1 1 2 1 1 1 3 3
## [519] 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 3 1 3 2 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 3 3 3 1 3 1
## 
## Within cluster sum of squares by cluster:
## [1] 2779.416 1314.407 1403.885
##  (between_SS / total_SS =  51.6 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k3_allvars = factor(kclust_k3_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k3_allvars)

Interpreting the clusters with k=3

breastCa_Re_new %>%
    group_by(kclust_k3_allvars) %>%
    summarize(across(c(2:21), mean))
LS0tDQp0aXRsZTogIlN0YXQtMjUzIEZpbmFsIFByb2plY3QiDQphdXRob3I6ICJKZW5ueSBMaSwgTGl6IENhbywgS3Jpc3R5IE1hIg0KZGF0ZTogJzIwMjItMDQtMDcnDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHRpZHkgPSBUUlVFKQ0KYGBgDQoNCiMgR2VuZXJhbCBTZXQgVVANCg0KIyMgTGlicmFyeQ0KYGBge3IsIGxpYnJhcnl9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHltb2RlbHMpIA0KbGlicmFyeShwcm9iYWJseSkNCmxpYnJhcnkodmlwKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KENsdXN0ZXJSKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeSh2aXApDQpsaWJyYXJ5KGd0KQ0KdGlkeW1vZGVsc19wcmVmZXIoKQ0KdGhlbWVfc2V0KHRoZW1lX2J3KCkpICAgICAgIA0KU3lzLnNldGxvY2FsZSgiTENfVElNRSIsICJFbmdsaXNoIikNCnNldC5zZWVkKDc0KQ0KYGBgDQoNCiMjIFJlYWQgaW4gZGF0YQ0KDQpgYGB7ciwgcmVhZGluZyBkYXRhfQ0KYnJlYXN0Q2E8LXJlYWRfY3N2KGZpbGUgPSAiYnJlYXN0LWNhbmNlci5jc3YiKQ0KYGBgDQoNCiMgUmVncmVzc2lvbiAoTGVhc3QgU3F1YXJlICYgTEFTU08gJiBHQU0pDQoNCiMjIERhdGEgY2xlYW5pbmcNCmBgYHtyfQ0KYnJlYXN0Q2FfUmU8LWJyZWFzdENhICU+JSANCiAgZHJvcF9uYSgpICU+JSANCiAgc2VsZWN0KHJhZGl1c19tZWFuOmZyYWN0YWxfZGltZW5zaW9uX21lYW4pIA0KDQpicmVhc3RDYV9SZV9uZXc8LWJyZWFzdENhX1JlJT4lDQogIG11dGF0ZShjb25jYXZlX3BvaW50c19tZWFuPWBjb25jYXZlIHBvaW50c19tZWFuYCklPiUNCiAgc2VsZWN0KC04KQ0KYGBgDQoNCiMjIENyZWF0aW9uIG9mIGN2IGZvbGRzDQpgYGB7cn0NCmJyZWFzdENhX1JlX0NWPC12Zm9sZF9jdihicmVhc3RDYV9SZSwgdiA9IDEwKQ0KYGBgDQoNCiMjIE1vZGVsIHNwZWMgKExlYXN0IFNxdWFyZSAmIExBU1NPKQ0KYGBge3J9DQojbGVhc3Qgc3F1YXJlDQpsbV9zcGVjIDwtDQogICAgbGluZWFyX3JlZygpICU+JSANCiAgICBzZXRfZW5naW5lKGVuZ2luZSA9ICdsbScpICU+JSANCiAgICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQoNCiNMQVNTTw0KbG1fbGFzc29fc3BlYyA8LSANCiAgbGluZWFyX3JlZygpICU+JQ0KICBzZXRfYXJncyhtaXh0dXJlID0gMSwgcGVuYWx0eSA9IHR1bmUoKSkgJT4lICMjIG1peHR1cmUgPSAxIGluZGljYXRlcyBMYXNzbw0KICBzZXRfZW5naW5lKGVuZ2luZSA9ICdnbG1uZXQnKSAlPiUgI25vdGUgd2UgYXJlIHVzaW5nIGEgZGlmZmVyZW50IGVuZ2luZQ0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpDQpgYGANCg0KIyMgUmVjaXBlcyAmIHdvcmtmbG93cyAoTGVhc3QgU3F1YXJlICYgTEFTU08pDQpgYGB7cn0NCiNsZWFzdCBzcXVhcmUNCmxlYXN0X3JlYyA8LSByZWNpcGUoYXJlYV9tZWFuIH4gLiwgZGF0YSA9IGJyZWFzdENhX1JlKSAlPiUNCiAgICBzdGVwX2NvcnIoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICAgIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JSAjIHJlbW92ZXMgdmFyaWFibGVzIHdpdGggdGhlIHNhbWUgdmFsdWUNCiAgICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JSAjIGltcG9ydGFudCBzdGFuZGFyZGl6YXRpb24gc3RlcCBmb3IgTEFTU08NCiAgICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkNCg0KbGVhc3RfbG1fd2YgPC0gd29ya2Zsb3coKSAlPiUNCiAgICBhZGRfcmVjaXBlKGxlYXN0X3JlYykgJT4lDQogICAgYWRkX21vZGVsKGxtX3NwZWMpDQogICAgDQojTEFTU08NCmxhc3NvX3dmPC0gd29ya2Zsb3coKSAlPiUgDQogIGFkZF9yZWNpcGUobGVhc3RfcmVjKSAlPiUNCiAgYWRkX21vZGVsKGxtX2xhc3NvX3NwZWMpIA0KYGBgDQoNCiMjIEZpdCAmIHR1bmUgbW9kZWxzIChMZWFzdCBTcXVhcmUgJiBMQVNTTykNCmBgYHtyfQ0KI2xlYXN0IHNxdWFyZQ0KbGVhc3RfZml0IDwtIGZpdChsZWFzdF9sbV93ZiwgZGF0YSA9IGJyZWFzdENhX1JlKSANCg0KbGVhc3RfZml0ICU+JSB0aWR5KCkNCg0KI0xBU1NPDQojdHVuZQ0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcigNCiAgcGVuYWx0eShyYW5nZSA9IGMoLTMsIDEpKSwgI2xvZzEwIHRyYW5zZm9ybWVkIA0KICBsZXZlbHMgPSAzMCkNCg0KdHVuZV9vdXRwdXQgPC0gdHVuZV9ncmlkKCAjIG5ldyBmdW5jdGlvbiBmb3IgdHVuaW5nIGh5cGVycGFyYW1ldGVycw0KICBsYXNzb193ZiwgIyB3b3JrZmxvdw0KICByZXNhbXBsZXMgPSBicmVhc3RDYV9SZV9DViwgIyBjdiBmb2xkcw0KICBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCBtYWUpLA0KICBncmlkID0gcGVuYWx0eV9ncmlkICMgcGVuYWx0eSBncmlkIGRlZmluZWQgYWJvdmUNCikNCg0KI2ZpdA0KYmVzdF9zZV9wZW5hbHR5IDwtIHNlbGVjdF9ieV9vbmVfc3RkX2Vycih0dW5lX291dHB1dCwgbWV0cmljID0gJ21hZScsIGRlc2MocGVuYWx0eSkpDQpmaW5hbF93Zl9zZSA8LSBmaW5hbGl6ZV93b3JrZmxvdyhsYXNzb193ZiwgYmVzdF9zZV9wZW5hbHR5KQ0KbGFzc29fZml0IDwtIGZpdChmaW5hbF93Zl9zZSAsIGRhdGEgPSBicmVhc3RDYV9SZSkNCmxhc3NvX2ZpdCAlPiUgdGlkeSgpDQoNCmBgYA0KDQojIyBDYWxjdWxhdGUgYW5kIGNvbGxlY3QgQ1YgbWV0cmljcyAoTGVhc3QgU3F1YXJlICYgTEFTU08pDQoNCmBgYHtyfQ0KIyBMZWFzdCBTcXVhcmUgbW9kZWwNCmxlYXN0X2ZpdF9jdiA8LSBmaXRfcmVzYW1wbGVzKGxlYXN0X2xtX3dmLA0KICByZXNhbXBsZXMgPSBicmVhc3RDYV9SZV9DViwgDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSkNCikNCg0KbGVhc3RfZml0X2N2ICU+JSBjb2xsZWN0X21ldHJpY3Moc3VtbWFyaXplID0gVFJVRSkNCmBgYA0KYGBge3J9DQojIExBU1NPIG1vZGVsDQp0dW5lX291dHB1dCAlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpICU+JSANCiAgZmlsdGVyKHBlbmFsdHkgPT0gKGJlc3Rfc2VfcGVuYWx0eSANCiAgICAgICAgICAgICAgICAgICAgICU+JSBwdWxsKHBlbmFsdHkpKSkNCmBgYA0KDQojIyBSZXNpZHVhbCBQbG90cyAoTGVhc3QgU3F1YXJlICYgTEFTU08pDQpgYGB7cn0NCiNsZWFzdCBzcXVhcmUNCmxlYXN0X2ZpdF9vdXRwdXQgPC0gbGVhc3RfZml0ICU+JQ0KICBwcmVkaWN0KG5ld19kYXRhID0gYnJlYXN0Q2FfUmUpICU+JQ0KICBiaW5kX2NvbHMoYnJlYXN0Q2FfUmUpICU+JQ0KICBtdXRhdGUocmVzaWQgPSBhcmVhX21lYW4gLSAucHJlZCkNCg0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gLnByZWQsIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh4ID0gIkZpdHRlZCB2YWx1ZXMiLCB5ID0gIlJlc2lkdWFscyIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gY29uY2F2aXR5X21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJDb25jYXZpdHkgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gY29tcGFjdG5lc3NfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkNvbXBhY3RuZXNzIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsZWFzdF9maXRfb3V0cHV0LCBhZXMoeCA9IGZyYWN0YWxfZGltZW5zaW9uX21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJGcmFjdGFsIGRpbWVuc2lvbiBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGVhc3RfZml0X291dHB1dCwgYWVzKHggPSByYWRpdXNfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIlJhZGl1cyBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpgYGB7cn0NCiNMQVNTTw0KbGFzc29fZml0X291dHB1dCA8LSBsYXNzb19maXQgJT4lDQogIHByZWRpY3QobmV3X2RhdGEgPSBicmVhc3RDYV9SZSkgJT4lDQogIGJpbmRfY29scyhicmVhc3RDYV9SZSkgJT4lDQogIG11dGF0ZShyZXNpZCA9IGFyZWFfbWVhbiAtIC5wcmVkKQ0KDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHggPSAiRml0dGVkIHZhbHVlcyIsIHkgPSAiUmVzaWR1YWxzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSBjb25jYXZpdHlfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkNvbmNhdml0eSBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSBjb21wYWN0bmVzc19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiQ29tcGFjdG5lc3MgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxhc3NvX2ZpdF9vdXRwdXQsIGFlcyh4ID0gZnJhY3RhbF9kaW1lbnNpb25fbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkZyYWN0YWwgZGltZW5zaW9uIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsYXNzb19maXRfb3V0cHV0LCBhZXMoeCA9IHJhZGl1c19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiUmFkaXVzIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIEdBTQ0KDQojIyMgQWNjb3VudGluZyBmb3Igbm9ubGluZWFyaXR5IA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0KZ2FtX3NwZWMgPC0gDQogIGdlbl9hZGRpdGl2ZV9tb2QoKSAlPiUNCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbWdjdicpICU+JQ0KICBzZXRfbW9kZSgncmVncmVzc2lvbicpIA0KDQpnYW1fbW9kIDwtIGZpdChnYW1fc3BlYywNCiAgICBhcmVhX21lYW4gfiBzKHJhZGl1c19tZWFuKSt0ZXh0dXJlX21lYW4rcyhwZXJpbWV0ZXJfbWVhbikrc21vb3RobmVzc19tZWFuK2NvbXBhY3RuZXNzX21lYW4rY29uY2F2ZV9wb2ludHNfbWVhbitjb25jYXZpdHlfbWVhbitzeW1tZXRyeV9tZWFuK2ZyYWN0YWxfZGltZW5zaW9uX21lYW4sDQogICAgZGF0YSA9IGJyZWFzdENhX1JlX25ldw0KKQ0KDQpwYXIobWZyb3c9YygyLDIpKQ0KZ2FtX21vZCAlPiUgcGx1Y2soJ2ZpdCcpICU+JSBtZ2N2OjpnYW0uY2hlY2soKSANCmdhbV9tb2QgJT4lIHBsdWNrKCdmaXQnKSAlPiUgc3VtbWFyeSgpDQoNCm5zX3JlYyA8LSBsZWFzdF9yZWMgJT4lDQogIHN0ZXBfbnMoeCwgZGVnX2ZyZWUgPSA5KQ0KbnM5X3dmIDwtIHdvcmtmbG93KCkgICU+JQ0KICBhZGRfcmVjaXBlKG5zX3JlYykgJT4lDQogIGFkZF9tb2RlbChsbV9zcGVjKQ0KDQpoaXN0KGJyZWFzdENhX1JlX25ldyRhcmVhX21lYW4pDQoNCmdhbV9tb2QgJT4lIHBsdWNrKCdmaXQnKSAlPiUgcGxvdCgpDQpgYGANCg0KIyMjIEV2YWx1YXRpb24gb2YgdGhlIEdBTSBtb2RlbCBvbiBUZXN0IERhdGEgDQoNCmBgYHtyfQ0KZ2FtX3Rlc3Rfb3V0cHV0IDwtIGdhbV9tb2QgJT4lDQogIHByZWRpY3QobmV3X2RhdGE9YnJlYXN0Q2FfUmVfbmV3KSAlPiUNCiAgYmluZF9jb2xzKGJyZWFzdENhX1JlX25ldyAlPiUgc2VsZWN0KGFyZWFfbWVhbikpDQoNCmdhbV90ZXN0X291dHB1dCAlPiUNCiAgcm1zZSh0cnV0aD1hcmVhX21lYW4sIGVzdGltYXRlPS5wcmVkKSANCg0KZ2FtX3Rlc3Rfb3V0cHV0ICU+JQ0KICBtYWUodHJ1dGg9YXJlYV9tZWFuLCBlc3RpbWF0ZT0ucHJlZCkgDQoNCmdhbV90ZXN0X291dHB1dCAlPiUNCiAgdGlkeSgpICU+JSANCiAgZ3QoKQ0KYGBgDQoNCiMgQ2xhc3NpZmljYXRpb24NCg0KIyMgRGF0YSBDbGVhbmluZw0KYGBge3J9DQpicmVhc3RDYV9SZTwtYnJlYXN0Q2EgJT4lIA0KICBkcm9wX25hKCkgJT4lIA0KICBzZWxlY3QoLWMoMTM6MjIpKSAlPiUgDQogIHNlbGVjdCgtMSkNCg0KYnJlYXN0Q2FfUmVfbmV3PC1icmVhc3RDYV9SZSU+JQ0KICBtdXRhdGUoY29uY2F2ZV9wb2ludHNfbWVhbj1gY29uY2F2ZSBwb2ludHNfbWVhbmApJT4lDQogIHNlbGVjdCgtMTApDQpgYGANCg0KIyMgTEFTU08gYW5kIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KIyMjIEltcGxldGUgTGFzc28gTG9naXN0aWMgUmVncmVzc2lvbiBpbiB0aWR5bW9kZWxzDQpgYGB7cn0NCg0KIyBNYWtlIHN1cmUgeW91IHNldCByZWZlcmVuY2UgbGV2ZWwgKHRvIHRoZSBvdXRjb21lIHlvdSBhcmUgTk9UIGludGVyZXN0ZWQgaW4pDQpicmVhc3RDYV9SZV9uZXcyIDwtIGJyZWFzdENhX1JlX25ldyU+JQ0KICBtdXRhdGUoZGlhZ25vc2lzID0gcmVsZXZlbChmYWN0b3IoZGlhZ25vc2lzICksIHJlZj0nQicpKSAjc2V0IHJlZmVyZW5jZSBsZXZlbA0KDQpkYXRhX2N2MTAgPC0gdmZvbGRfY3YoYnJlYXN0Q2FfUmVfbmV3MiwgdiA9IDEwKQ0KDQoNCiMgTG9naXN0aWMgTEFTU08gUmVncmVzc2lvbiBNb2RlbCBTcGVjDQpsb2dpc3RpY19sYXNzb19zcGVjX3R1bmUgPC0gbG9naXN0aWNfcmVnKCkgJT4lDQogICAgc2V0X2VuZ2luZSgnZ2xtbmV0JykgJT4lDQogICAgc2V0X2FyZ3MobWl4dHVyZSA9IDEsIHBlbmFsdHkgPSB0dW5lKCkpICU+JQ0KICAgIHNldF9tb2RlKCdjbGFzc2lmaWNhdGlvbicpDQoNCiMgUmVjaXBlDQpsb2dpc3RpY19yZWMgPC0gcmVjaXBlKGRpYWdub3NpcyB+IC4sIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKSAlPiUNCiAgICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JSANCiAgICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkNCg0KIyBXb3JrZmxvdyAoUmVjaXBlICsgTW9kZWwpDQpsb2dfbGFzc29fd2YgPC0gd29ya2Zsb3coKSAlPiUgDQogICAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWMpICU+JQ0KICAgIGFkZF9tb2RlbChsb2dpc3RpY19sYXNzb19zcGVjX3R1bmUpIA0KDQojIFR1bmUgTW9kZWwgKHRyeWluZyBhIHZhcmlldHkgb2YgdmFsdWVzIG9mIExhbWJkYSBwZW5hbHR5KQ0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcigNCiAgcGVuYWx0eShyYW5nZSA9IGMoLTUsIDEpKSwgI2xvZzEwIHRyYW5zZm9ybWVkICAoa2VwdCBtb3ZpbmcgbWluIGRvd24gZnJvbSAwKQ0KICBsZXZlbHMgPSAxMDApDQoNCnR1bmVfb3V0cHV0IDwtIHR1bmVfZ3JpZCggDQogIGxvZ19sYXNzb193ZiwgIyB3b3JrZmxvdw0KICByZXNhbXBsZXMgPSBkYXRhX2N2MTAsICMgY3YgZm9sZHMNCiAgbWV0cmljcyA9IG1ldHJpY19zZXQocm9jX2F1YyxhY2N1cmFjeSksDQogIGNvbnRyb2wgPSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFLCBldmVudF9sZXZlbCA9ICdzZWNvbmQnKSwNCiAgZ3JpZCA9IHBlbmFsdHlfZ3JpZCAjIHBlbmFsdHkgZ3JpZCBkZWZpbmVkIGFib3ZlDQopDQoNCiMgVmlzdWFsaXplIE1vZGVsIEV2YWx1YXRpb24gTWV0cmljcyBmcm9tIFR1bmluZw0KYXV0b3Bsb3QodHVuZV9vdXRwdXQpICsgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMjIEluc3BlY3RpbmcgdGhlIE1vZGVsDQpgYGB7cn0NCmJlc3Rfc2VfcGVuYWx0eSA8LSBzZWxlY3RfYnlfb25lX3N0ZF9lcnIodHVuZV9vdXRwdXQsIG1ldHJpYyA9ICdyb2NfYXVjJywgZGVzYyhwZW5hbHR5KSkgIyBjaG9vc2UgcGVuYWx0eSB2YWx1ZSBiYXNlZCBvbiB0aGUgbGFyZ2VzdCBwZW5hbHR5IHdpdGhpbiAxIHNlIG9mIHRoZSBoaWdoZXN0IENWIHJvY19hdWMNCmZpbmFsX2ZpdF9zZSA8LSBmaW5hbGl6ZV93b3JrZmxvdyhsb2dfbGFzc29fd2YsIGJlc3Rfc2VfcGVuYWx0eSkgJT4lICMgaW5jb3Jwb3JhdGVzIHBlbmFsdHkgdmFsdWUgdG8gd29ya2Zsb3cgDQogICAgZml0KGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KDQpmaW5hbF9maXRfc2UgJT4lIHRpZHkoKQ0KDQpmaW5hbF9maXRfc2UgJT4lIHRpZHkoKSAlPiUNCiAgZmlsdGVyKGVzdGltYXRlID09IDApDQoNCiN2YXJpYWJsZSBpbXBvcnRhbmNlDQpnbG1uZXRfb3V0cHV0IDwtIGZpbmFsX2ZpdF9zZSAlPiUgZXh0cmFjdF9maXRfZW5naW5lKCkNCiAgICANCiMgQ3JlYXRlIGEgYm9vbGVhbiBtYXRyaXggKHByZWRpY3RvcnMgeCBsYW1iZGFzKSBvZiB2YXJpYWJsZSBleGNsdXNpb24NCmJvb2xfcHJlZGljdG9yX2V4Y2x1ZGUgPC0gZ2xtbmV0X291dHB1dCRiZXRhPT0wDQoNCiMgTG9vcCBvdmVyIGVhY2ggdmFyaWFibGUNCnZhcl9pbXAgPC0gc2FwcGx5KHNlcV9sZW4obnJvdyhib29sX3ByZWRpY3Rvcl9leGNsdWRlKSksIGZ1bmN0aW9uKHJvdykgew0KICAgICMgRXh0cmFjdCBjb2VmZmljaWVudCBwYXRoIChzb3J0ZWQgZnJvbSBoaWdoZXN0IHRvIGxvd2VzdCBsYW1iZGEpDQogICAgdGhpc19jb2VmZl9wYXRoIDwtIGJvb2xfcHJlZGljdG9yX2V4Y2x1ZGVbcm93LF0NCiAgICAjIENvbXB1dGUgYW5kIHJldHVybiB0aGUgIyBvZiBsYW1iZGFzIHVudGlsIHRoaXMgdmFyaWFibGUgaXMgb3V0IGZvcmV2ZXINCiAgICBuY29sKGJvb2xfcHJlZGljdG9yX2V4Y2x1ZGUpIC0gd2hpY2gubWluKHRoaXNfY29lZmZfcGF0aCkgKyAxDQp9KQ0KDQojIENyZWF0ZSBhIGRhdGFzZXQgb2YgdGhpcyBpbmZvcm1hdGlvbiBhbmQgc29ydA0KdmFyX2ltcF9kYXRhIDwtIHRpYmJsZSgNCiAgICB2YXJfbmFtZSA9IHJvd25hbWVzKGJvb2xfcHJlZGljdG9yX2V4Y2x1ZGUpLA0KICAgIHZhcl9pbXAgPSB2YXJfaW1wDQopDQp2YXJfaW1wX2RhdGEgJT4lIGFycmFuZ2UoZGVzYyh2YXJfaW1wKSkNCmBgYA0KDQojIyMgRXZhbHVhdGlvbiBNZXRyaWNzDQpgYGB7cn0NCiMgQ1YgcmVzdWx0cyBmb3IgImJlc3QgbGFtYmRhIg0KdHVuZV9vdXRwdXQgJT4lDQogICAgY29sbGVjdF9tZXRyaWNzKCkgJT4lDQogICAgZmlsdGVyKHBlbmFsdHkgPT0gYmVzdF9zZV9wZW5hbHR5ICU+JSBwdWxsKHBlbmFsdHkpKQ0KDQojIENvdW50IHVwIG51bWJlciBvZiBCIGFuZCBNIGluIHRoZSB0cmFpbmluZyBkYXRhDQpicmVhc3RDYV9SZV9uZXcyICU+JQ0KICAgIGNvdW50KGRpYWdub3NpcykgIyBOYW1lIG9mIHRoZSBvdXRjb21lIHZhcmlhYmxlIGdvZXMgaW5zaWRlIGNvdW50KCkNCg0KI0NvbXB1dGUgdGhlIE5JUg0KTklSPC0gMzU3LygzNTcrMjEyKQ0KTklSDQpgYGANCg0KIyMjIFRocmVzaG9sZA0KYGBge3J9DQojIFNvZnQgUHJlZGljdGlvbnMgb24gVHJhaW5pbmcgRGF0YQ0KZmluYWxfb3V0cHV0IDwtDQogIGZpbmFsX2ZpdF9zZSAlPiUgcHJlZGljdChuZXdfZGF0YSA9IGJyZWFzdENhX1JlX25ldzIsIHR5cGUgPSAncHJvYicpICU+JSAgICAgYmluZF9jb2xzKGJyZWFzdENhX1JlX25ldzIpDQoNCg0KDQpmaW5hbF9vdXRwdXQgJT4lDQogIGdncGxvdChhZXMoeCA9IGRpYWdub3NpcywgeSA9IC5wcmVkX00pKSArDQogIGdlb21fYm94cGxvdCgpDQoNCiMgVXNlIHNvZnQgcHJlZGljdGlvbnMNCmZpbmFsX291dHB1dCAlPiUNCiAgICByb2NfY3VydmUoZGlhZ25vc2lzLC5wcmVkX00sZXZlbnRfbGV2ZWwgPSAnc2Vjb25kJykgJT4lDQogICAgYXV0b3Bsb3QoKQ0KDQojIHRocmVzaG9sZHMgaW4gdGVybXMgb2YgcmVmZXJlbmNlIGxldmVsDQp0aHJlc2hvbGRfb3V0cHV0IDwtIGZpbmFsX291dHB1dCAlPiUNCiAgICB0aHJlc2hvbGRfcGVyZih0cnV0aCA9IGRpYWdub3NpcywgZXN0aW1hdGUgPSAucHJlZF9CLCB0aHJlc2hvbGRzID0gc2VxKDAsMSxieT0uMDEpKSANCg0KIyBKLWluZGV4IHYuIHRocmVzaG9sZCBmb3Igbm90IE0NCnRocmVzaG9sZF9vdXRwdXQgJT4lDQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2pfaW5kZXgnKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHggPSAudGhyZXNob2xkLCB5ID0gLmVzdGltYXRlKSkgKw0KICAgIGdlb21fbGluZSgpICsNCiAgICBsYWJzKHkgPSAnSi1pbmRleCcsIHggPSAndGhyZXNob2xkJykgKw0KICAgIHRoZW1lX2NsYXNzaWMoKQ0KDQp0aHJlc2hvbGRfb3V0cHV0ICU+JQ0KICAgIGZpbHRlcigubWV0cmljID09ICdqX2luZGV4JykgJT4lDQogICAgYXJyYW5nZShkZXNjKC5lc3RpbWF0ZSkpDQoNCiMgRGlzdGFuY2Ugdi4gdGhyZXNob2xkIGZvciBub3QgTQ0KDQp0aHJlc2hvbGRfb3V0cHV0ICU+JQ0KICAgIGZpbHRlcigubWV0cmljID09ICdkaXN0YW5jZScpICU+JQ0KICAgIGdncGxvdChhZXMoeCA9IC50aHJlc2hvbGQsIHkgPSAuZXN0aW1hdGUpKSArDQogICAgZ2VvbV9saW5lKCkgKw0KICAgIGxhYnMoeSA9ICdEaXN0YW5jZScsIHggPSAndGhyZXNob2xkJykgKw0KICAgIHRoZW1lX2NsYXNzaWMoKQ0KDQp0aHJlc2hvbGRfb3V0cHV0ICU+JQ0KICAgIGZpbHRlcigubWV0cmljID09ICdkaXN0YW5jZScpICU+JQ0KICAgIGFycmFuZ2UoLmVzdGltYXRlKQ0KDQpsb2dfbWV0cmljcyA8LSBtZXRyaWNfc2V0KGFjY3VyYWN5LHNlbnMseWFyZHN0aWNrOjpzcGVjKQ0KDQpmaW5hbF9vdXRwdXQgJT4lDQogICAgbXV0YXRlKC5wcmVkX2NsYXNzID0gbWFrZV90d29fY2xhc3NfcHJlZCgucHJlZF9CLCBsZXZlbHMoZGlhZ25vc2lzKSwgdGhyZXNob2xkID0gLjY0KSkgJT4lDQogICAgbG9nX21ldHJpY3ModHJ1dGggPSBkaWFnbm9zaXMsIGVzdGltYXRlID0gLnByZWRfY2xhc3MsIGV2ZW50X2xldmVsID0gJ3NlY29uZCcpDQpgYGANCg0KIyMgUmFuZG9tIEZvcmVzdA0KDQojIyMgQnVpbGRpbmcgUmFuZG9tIEZvcmVzdA0KYGBge3J9DQojIE1vZGVsIFNwZWNpZmljYXRpb24NCnJmX3NwZWMgPC0gcmFuZF9mb3Jlc3QoKSAlPiUNCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAncmFuZ2VyJykgJT4lIA0KICBzZXRfYXJncyhtdHJ5ID0gTlVMTCwgIyBzaXplIG9mIHJhbmRvbSBzdWJzZXQgb2YgdmFyaWFibGVzOyBkZWZhdWx0IGlzIGZsb29yKHNxcnQobmNvbCh4KSkpDQogICAgICAgICAgIHRyZWVzID0gMTAwMCwgIyBOdW1iZXIgb2YgdHJlZXMNCiAgICAgICAgICAgbWluX24gPSAyLA0KICAgICAgICAgICBwcm9iYWJpbGl0eSA9IEZBTFNFLCAjIEZBTFNFOiBoYXJkIHByZWRpY3Rpb25zDQogICAgICAgICAgIGltcG9ydGFuY2UgPSAnaW1wdXJpdHknKSAlPiUgDQogIHNldF9tb2RlKCdjbGFzc2lmaWNhdGlvbicpICMgY2hhbmdlIHRoaXMgZm9yIHJlZ3Jlc3Npb24gdHJlZQ0KDQojIFJlY2lwZQ0KZGF0YV9yZWMgPC0gcmVjaXBlKGRpYWdub3NpcyB+IC4sIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KDQojIFdvcmtmbG93cw0KZGF0YV93Zl9tdHJ5MiA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfbW9kZWwocmZfc3BlYyAlPiUgc2V0X2FyZ3MobXRyeSA9IDIpKSAlPiUNCiAgYWRkX3JlY2lwZShkYXRhX3JlYykNCg0KIyBDcmVhdGUgd29ya2Zsb3dzIGZvciBtdHJ5ID0gNCAsIDEwLCBhbmQgMjANCmRhdGFfd2ZfbXRyeTQgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSA0KSkgJT4lDQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpDQoNCmRhdGFfd2ZfbXRyeTEwIDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gMTApKSAlPiUNCiAgYWRkX3JlY2lwZShkYXRhX3JlYykNCg0KZGF0YV93Zl9tdHJ5MjAgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSAyMCkpICU+JQ0KICBhZGRfcmVjaXBlKGRhdGFfcmVjKQ0KYGBgDQoNCmBgYHtyfQ0KIyBGaXQgTW9kZWxzDQoNCnNldC5zZWVkKDEyMykgIyBtYWtlIHN1cmUgdG8gcnVuIHRoaXMgYmVmb3JlIGVhY2ggZml0IHNvIHRoYXQgeW91IGhhdmUgdGhlIHNhbWUgMTAwMCB0cmVlcw0KZGF0YV9maXRfbXRyeTIgPC0gZml0KGRhdGFfd2ZfbXRyeTIsIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KDQpzZXQuc2VlZCgxMjMpDQpkYXRhX2ZpdF9tdHJ5NCA8LSBmaXQoZGF0YV93Zl9tdHJ5NCwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQoNCnNldC5zZWVkKDEyMykgDQpkYXRhX2ZpdF9tdHJ5MTAgPC0gZml0KGRhdGFfd2ZfbXRyeTEwLCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCg0Kc2V0LnNlZWQoMTIzKQ0KZGF0YV9maXRfbXRyeTIwIDwtIGZpdChkYXRhX3dmX210cnkyMCwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQpgYGANCg0KYGBge3J9DQojIEN1c3RvbSBGdW5jdGlvbiB0byBnZXQgT09CIHByZWRpY3Rpb25zLCB0cnVlIG9ic2VydmVkIG91dGNvbWVzIGFuZCBhZGQgYSBtb2RlbCBsYWJlbA0KcmZfT09CX291dHB1dCA8LSBmdW5jdGlvbihmaXRfbW9kZWwsIG1vZGVsX2xhYmVsLCB0cnV0aCl7DQogICAgdGliYmxlKA0KICAgICAgICAgIC5wcmVkX2RpYWdub3NpcyA9IGZpdF9tb2RlbCAlPiUgZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lIHBsdWNrKCdwcmVkaWN0aW9ucycpLCAjT09CIHByZWRpY3Rpb25zDQogICAgICAgICAgZGlhZ25vc2lzID0gdHJ1dGgsDQogICAgICAgICAgbW9kZWwgPSBtb2RlbF9sYWJlbA0KICAgICAgKQ0KfQ0KDQojY2hlY2sgb3V0IHRoZSBmdW5jdGlvbiBvdXRwdXQNCnJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTIsJ210cnkyJywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBFdmFsdWF0ZSBPT0IgTWV0cmljcw0KDQpkYXRhX3JmX09PQl9vdXRwdXQgPC0gYmluZF9yb3dzKA0KICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTIsJ210cnkyJywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKSwNCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk0LCdtdHJ5NCcsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSksDQogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MTAsJ210cnkxMCcsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSksDQogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MjAsJ210cnkyMCcsIGJyZWFzdENhX1JlX25ldzIgJT4lIHB1bGwoZGlhZ25vc2lzKSkNCikNCg0KDQpkYXRhX3JmX09PQl9vdXRwdXQgJT4lIA0KICAgIGdyb3VwX2J5KG1vZGVsKSAlPiUNCiAgICBhY2N1cmFjeSh0cnV0aCA9IGRpYWdub3NpcywgZXN0aW1hdGUgPSAucHJlZF9kaWFnbm9zaXMpDQpgYGANCg0KIyMjIFByZWxpbWluYXJ5IGludGVycHJldGF0aW9uDQpgYGB7cn0NCmRhdGFfcmZfT09CX291dHB1dCAlPiUgDQogICAgZ3JvdXBfYnkobW9kZWwpICU+JQ0KICAgIGFjY3VyYWN5KHRydXRoID0gZGlhZ25vc2lzLCBlc3RpbWF0ZSA9LnByZWRfZGlhZ25vc2lzKSAlPiUNCiAgbXV0YXRlKG10cnkgPSBhcy5udW1lcmljKHN0cmluZ3I6OnN0cl9yZXBsYWNlKG1vZGVsLCdtdHJ5JywnJykpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbXRyeSwgeSA9IC5lc3RpbWF0ZSApKSArIA0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIyBFdmFsdWF0aW5nIHRoZSBmb3Jlc3QNCmBgYHtyfQ0KZGF0YV9maXRfbXRyeTINCmBgYA0KDQpgYGB7cn0NCnJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTIsJ210cnkyJywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKSAlPiUNCiAgICBjb25mX21hdCh0cnV0aCA9IGRpYWdub3NpcywgZXN0aW1hdGU9IC5wcmVkX2RpYWdub3NpcykNCg0KYGBgDQoNCiMjIyBWYXJpYWJsZSBpbXBvcnRhbmNlIG1lYXN1cmVzDQpgYGB7cn0NCmRhdGFfZml0X210cnkyICU+JSANCiAgICBleHRyYWN0X2ZpdF9lbmdpbmUoKSAlPiUgDQogICAgdmlwKG51bV9mZWF0dXJlcyA9IDMwKSArIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGJyZWFzdENhX1JlX25ldzIsIGFlcyh4ID0gZGlhZ25vc2lzLCB5ID0gYXJlYV93b3JzdCkpICsNCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoYnJlYXN0Q2FfUmVfbmV3MiwgYWVzKHggPSBkaWFnbm9zaXMsIHkgPSBmcmFjdGFsX2RpbWVuc2lvbl9tZWFuKSkgKw0KICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpgYGB7cn0NCiNpbnRlcm1lZGlhdGUgaW1wb3J0YW50DQpnZ3Bsb3QoYnJlYXN0Q2FfUmVfbmV3MiwgYWVzKHggPSBkaWFnbm9zaXMsIHkgPSBwZXJpbWV0ZXJfbWVhbikpICsNCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyBDbHVzdGVyaW5nDQoNCiMjIERhdGEgQ2xlYW5pbmcNCmBgYHtyfQ0KYnJlYXN0Q2FfUmU8LWJyZWFzdENhICU+JSANCiAgZHJvcF9uYSgpICU+JSANCiAgc2VsZWN0KC1jKDEzOjIyKSkgJT4lIA0KICBzZWxlY3QoLTEpDQoNCmJyZWFzdENhX1JlX25ldzwtYnJlYXN0Q2FfUmUlPiUNCiAgbXV0YXRlKGNvbmNhdmVfcG9pbnRzX21lYW49YGNvbmNhdmUgcG9pbnRzX21lYW5gKSU+JQ0KICBzZWxlY3QoLTEwKSANCmBgYA0KDQpgYGB7cn0NCmdncGxvdChicmVhc3RDYV9SZV9uZXcsIGFlcyh4ID0gcGVyaW1ldGVyX21lYW4sIHkgPSBgY29uY2F2ZSBwb2ludHNfd29yc3RgKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMgSy1tZWFucyBjbHVzdGVyaW5nIG9uIHBlcmltZXRlcl9tZWFuIGFuZCBjb25jYXZlIHBvaW50c193b3JzdA0KYGBge3J9DQojIFNlbGVjdCBqdXN0IHRoZSBwZXJpbWV0ZXJfbWVhbiBhbmQgY29uY2F2ZSBwb2ludHNfd29yc3QgdmFyaWFibGVzDQpicmVhc3RDYV9SZV9uZXdfc3ViIDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBzZWxlY3QocGVyaW1ldGVyX21lYW4sIGBjb25jYXZlIHBvaW50c193b3JzdGApDQoNCiMgUnVuIGstbWVhbnMgZm9yIGsgPSBjZW50ZXJzID0gMg0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2syIDwtIGttZWFucyhicmVhc3RDYV9SZV9uZXdfc3ViLCBjZW50ZXJzID0gMikNCg0KIyBEaXNwbGF5IHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzDQprY2x1c3RfazIkY2x1c3Rlcg0KYGBgDQoNCmBgYHtyfQ0KIyBBZGQgYSB2YXJpYWJsZSAoa2NsdXN0X2syKSB0byB0aGUgb3JpZ2luYWwgZGF0YXNldCANCiMgY29udGFpbmluZyB0aGUgY2x1c3RlciBhc3NpZ25tZW50cw0KYnJlYXN0Q2FfUmVfbmV3IDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBtdXRhdGUoa2NsdXN0XzIgPSBmYWN0b3Ioa2NsdXN0X2syJGNsdXN0ZXIpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBWaXN1YWxpemUgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgb24gdGhlIG9yaWdpbmFsIHNjYXR0ZXJwbG90DQpvcmlnaW5hbENsdXN0ZXJQbG90IDwtIGdncGxvdCgNCiAgYnJlYXN0Q2FfUmVfbmV3LA0KICBhZXMoDQogICAgeCA9IHBlcmltZXRlcl9tZWFuLA0KICAgIHkgPSBgY29uY2F2ZSBwb2ludHNfd29yc3RgLA0KICAgIGNvbG9yID0ga2NsdXN0XzIsDQogICAgdGV4dCA9IHBhc3RlKCdkaWFnbm9zaXM6ICcsIGRpYWdub3NpcykNCiAgKQ0KKSArDQogIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQpnZ3Bsb3RseShvcmlnaW5hbENsdXN0ZXJQbG90ICAsIHRvb2x0aXAgPSBjKCAidGV4dCIpKQ0KYGBgDQoNCiMjIyBBZGRyZXNzaW5nIHZhcmlhYmxlIHNjYWxlDQoNCmBgYHtyfQ0KIyBSdW4gay1tZWFucyBvbiB0aGUgKnNjYWxlZCogZGF0YSAoYWxsIHZhcmlhYmxlcyBoYXZlIFNEID0gMSkNCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rMl9zY2FsZSA8LSBrbWVhbnMoc2NhbGUoYnJlYXN0Q2FfUmVfbmV3X3N1YiksIGNlbnRlcnMgPSAyKQ0KYnJlYXN0Q2FfUmVfbmV3IDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBtdXRhdGUoa2NsdXN0XzJfc2NhbGUgPSBmYWN0b3Ioa2NsdXN0X2syX3NjYWxlJGNsdXN0ZXIpKQ0KDQojIFZpc3VhbGl6ZSB0aGUgbmV3IGNsdXN0ZXIgYXNzaWdubWVudHMNCnNjYWxlZENsdXN0ZXJQbG90IDwtIGdncGxvdCgNCiAgYnJlYXN0Q2FfUmVfbmV3LA0KICBhZXMoDQogICAgeCA9IHBlcmltZXRlcl9tZWFuLA0KICAgIHkgPSBgY29uY2F2ZSBwb2ludHNfd29yc3RgLA0KICAgIGNvbG9yID0ga2NsdXN0XzIsDQogICAgdGV4dCA9IHBhc3RlKCdkaWFnbm9zaXM6ICcsIGRpYWdub3NpcykNCiAgKQ0KKSArDQogIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQpnZ3Bsb3RseShzY2FsZWRDbHVzdGVyUGxvdCAgLCB0b29sdGlwID0gYyggInRleHQiKSkNCmBgYA0KDQoNCiMjIyBDbHVzdGVyaW5nIG9uIG1vcmUgdmFyaWFibGVzDQpgYGB7cn0NCiMgU2VsZWN0IHRoZSB2YXJpYWJsZXMgdG8gYmUgdXNlZCBpbiBjbHVzdGVyaW5nDQpicmVhc3RDYV9SZV9uZXdfc3ViMiA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgc2VsZWN0KGMoMjoyMSkpDQoNCiMgTG9vayBhdCBzdW1tYXJ5IHN0YXRpc3RpY3Mgb2YgdGhlIDMgdmFyaWFibGVzDQpzdW1tYXJ5KGJyZWFzdENhX1JlX25ld19zdWIyKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2syX2FsbHZhcnMgPC0ga21lYW5zKHNjYWxlKGJyZWFzdENhX1JlX25ld19zdWIyKSwgY2VudGVycyA9IDIpDQoNCmJyZWFzdENhX1JlX25ldyA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgbXV0YXRlKGtjbHVzdF9rMl9hbGx2YXJzID0gZmFjdG9yKGtjbHVzdF9rMl9hbGx2YXJzJGNsdXN0ZXIpKQ0KDQoNCmJyZWFzdENhX1JlX25ldyAlPiUNCiAgY291bnQoZGlhZ25vc2lzLGtjbHVzdF9rMl9hbGx2YXJzKQ0KYGBgDQoNCiMjIyBJbnRlcnByZXRpbmcgdGhlIGNsdXN0ZXJzDQpgYGB7cn0NCmJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBncm91cF9ieShrY2x1c3RfazJfYWxsdmFycykgJT4lDQogICAgc3VtbWFyaXplKGFjcm9zcyhjKDI6MjEpLCBtZWFuKSkNCmBgYA0KDQojIyMgUGlja2luZyBrDQpgYGB7cn0NCiMgRGF0YS1zcGVjaWZpYyBmdW5jdGlvbiB0byBjbHVzdGVyIGFuZCBjYWxjdWxhdGUgdG90YWwgd2l0aGluLWNsdXN0ZXIgU1MNCmJyZWFzdENhX1JlX25ld19jbHVzdGVyX3NzIDwtIGZ1bmN0aW9uKGspew0KICAgICMgUGVyZm9ybSBjbHVzdGVyaW5nDQogICAga2NsdXN0IDwtIGttZWFucyhzY2FsZShicmVhc3RDYV9SZV9uZXdfc3ViMiksIGNlbnRlcnMgPSBrKQ0KDQogICAgIyBSZXR1cm4gdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzDQogICAgcmV0dXJuKGtjbHVzdCR0b3Qud2l0aGluc3MpDQp9DQoNCnRpYmJsZSgNCiAgICBrID0gMToyMCwNCiAgICB0b3Rfd2Nfc3MgPSBwdXJycjo6bWFwX2RibCgxOjIwLCBicmVhc3RDYV9SZV9uZXdfY2x1c3Rlcl9zcykNCikgJT4lIA0KICAgIGdncGxvdChhZXMoeCA9IGssIHkgPSB0b3Rfd2Nfc3MpKSArDQogICAgZ2VvbV9wb2ludCgpICsgDQogICAgZ2VvbV9saW5lKCkrDQogICAgbGFicyh4ID0gIk51bWJlciBvZiBjbHVzdGVycyIseSA9ICdUb3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcycpICsgDQogICAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMjIE5vcm1hbGl6ZWQgdmFyaWFibGVzIGNsdXN0ZXJpbmcgdmlzdWFsaXphaXRvbiBvZiBvcHRpbWFsIEsNCmBgYHtyfQ0KIyBSdW4gay1tZWFucyBmb3IgayA9IGNlbnRlcnMgPSAzDQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazMgPC0ga21lYW5zKGJyZWFzdENhX1JlX25ld19zdWIsIGNlbnRlcnMgPSAzKQ0KDQojIERpc3BsYXkgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMNCmtjbHVzdF9rMyRjbHVzdGVyDQoNCiMgUnVuIGstbWVhbnMgb24gdGhlICpzY2FsZWQqIGRhdGEgKGFsbCB2YXJpYWJsZXMgaGF2ZSBTRCA9IDEpDQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazNfc2NhbGUgPC0ga21lYW5zKHNjYWxlKGJyZWFzdENhX1JlX25ld19zdWIpLCBjZW50ZXJzID0gMykNCmJyZWFzdENhX1JlX25ldyA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgbXV0YXRlKGtjbHVzdF8zX3NjYWxlID0gZmFjdG9yKGtjbHVzdF9rM19zY2FsZSRjbHVzdGVyKSkNCg0KIyBWaXN1YWxpemUgdGhlIG5ldyBjbHVzdGVyIGFzc2lnbm1lbnRzDQpuZXdDbHVzdGVyS1Bsb3QgPC0gZ2dwbG90KA0KICBicmVhc3RDYV9SZV9uZXcsDQogIGFlcygNCiAgICB4ID0gcGVyaW1ldGVyX21lYW4sDQogICAgeSA9IGBjb25jYXZlIHBvaW50c193b3JzdGAsDQogICAgY29sb3IgPSBrY2x1c3RfM19zY2FsZSwNCiAgICB0ZXh0ID0gcGFzdGUoJ2RpYWdub3NpczogJywgZGlhZ25vc2lzKQ0KICApDQopICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmdncGxvdGx5KG5ld0NsdXN0ZXJLUGxvdCAgLCB0b29sdGlwID0gYyggInRleHQiKSkNCmBgYA0KDQojIyMgUGVyZm9ybSBjbHVzdGVyaW5nIG9uIG1vcmUgdmFyaWFiZWxzIHdpdGggSz0zDQpgYGB7cn0NCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rM19hbGx2YXJzIDwtIGttZWFucyhzY2FsZShicmVhc3RDYV9SZV9uZXdfc3ViMiksIGNlbnRlcnMgPSAzKQ0KI3dpdGhpbiBjbHVzdGVycyBzdSBvZiBzcXVhcmVzDQprY2x1c3RfazNfYWxsdmFycw0KDQpicmVhc3RDYV9SZV9uZXcgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIG11dGF0ZShrY2x1c3RfazNfYWxsdmFycyA9IGZhY3RvcihrY2x1c3RfazNfYWxsdmFycyRjbHVzdGVyKSkNCg0KDQpicmVhc3RDYV9SZV9uZXcgJT4lDQogIGNvdW50KGRpYWdub3NpcyxrY2x1c3RfazNfYWxsdmFycykNCmBgYA0KDQojIyMgSW50ZXJwcmV0aW5nIHRoZSBjbHVzdGVycyB3aXRoIGs9Mw0KYGBge3J9DQpicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgZ3JvdXBfYnkoa2NsdXN0X2szX2FsbHZhcnMpICU+JQ0KICAgIHN1bW1hcml6ZShhY3Jvc3MoYygyOjIxKSwgbWVhbikpDQpgYGA=